# 接入 Inference General Perf

在 ByteMLPerf Inference General Perf 的系统架构设计中，框架和后端是隔离的，供应商可以自行实现后端，并作为ByteMLPerf的后端参与评估测试。

## 后端开发

### 创建后端

- 在backends/文件夹下创建一个以后端名称命名的新文件夹。所有所需的文件都需要存储在此目录中，例如GRAPHCORE后端，其目录名称为GRAPHCORE（有关具体命名规则，请参阅下面的命名规则）。

- 添加compile_backend_xxx.py/runtime_backend_xxx.py，其中xxx是后端名称，例如GRAPHCORE后端。您需要创建一个名为compile_backend_graphcore.py的入口文件，该文件需要内部继承CompileBackend类；

- 添加xxx.json，用于与用户互动。如果不需要与用户互动，您可以直接在backend_xxx.py的get_interact_profile处提供一个空文件并直接返回None；

- 添加requirements.txt，用于所需的环境依赖项，框架会为每个后端创建一个新的venv，并为其安装在requirements中声明的pkg；

我们使用Graphcore作为例子。后端应该包含以下文件：

```bash
byte_mlperf/backends/GRAPHCORE/
├── compile_backend_graphcore.py
├── runtime_backend_graphcore.py
├── GRAPHCORE.json
└── requirements.txt
```

### 实现 CompileBackend API

关于CompileBackend基类，请参考下面的Compile Backend部分。

在当前版本中，需要实现的APIs如下：
- **pre_optimize()**
模型预优化接口。在编译前预先优化模型，如模型排序、Input Shape等。允许更改模型结构，但后端需要在格式更改后保存模型，以确保仍然可以加载和运行原始模型。
如果不需要，此接口可以不实现。

- **compile()**
模型编译接口。对于需要进行编译的供应商，可以在此执行模型转换和编译。这里可以更改模型格式，并且编译后的产品可以由运行时后端加载和运行，或者可以由QS Runtime加载和运行。
此外，除了返回编译后的产品外，compile还需要返回编译后的编译精度、子图分割信息以及模型IO信息（如果不是全图编译且运行时支持异构操作）。

```python
result = {
    "model": "ResNet50",
    "framework": "Tensorflow",
    "compile_precision": "int16_mix",
    "optimizations": {},
    "instance_count": 1, // The number of total machines was used
    "device_count": 128, // The number of total cards was used
    "input_type": ["INT16"], //List of String, Upper case only
    "max_batch_size": 64, //Max Batch Size allowed to use
    "compile_status": "success", //Only if all subgraph was compiled successfully
    "sg_percent": 100,
    "segments": [{
        "sg_idx": 0,
        "is_fallback" : false,
        "input_tensor_map" : {"input:0":[-1,3,255,255]},
        "output_tensor_map" : {"pred:0":[-1,1024]},
        "compiled_model" : [{
            "compiled_bs" : 1,         
            "compiled_obj" : "xxx.obj",
        },],
    },]
}
```

如上例所示，如果编译时生成了多个子图，需要返回多个段；如果编译了多个批处理大小，需要在compiled_model中列出所有批处理大小。

需要注意的是，is_fallback字段表示当前子图是否会回退到CPU上运行。如果为true，通常意味着当前子图没有放置在加速卡上，而是回退到CPU上执行。

注意：如果需要在compile()中使用数据加载器，可以参考上面的ModelZoo＆Dataset部分。

- **get_interact_profile()**
加载交互式配置接口。如果供应商需要用户提供一些额外的信息，例如编译配置，精度配置，您可以在此处加载添加的json文件，并返回一个字典列表。Framework会向用户显示配置文件的内容，并负责收集有关配置文件的反馈。如果用户不需要提供额外的信息，则此处返回None。

```json title="CPU.json"
[
    {
        "name": "omp",
        "note": "Using OMP？",
        "dialog_type": "Yes/No Dialog",
        "type": "bool",
        "default": false,
        "depends": null
    },
    {
        "name": "precision",
        "note": "Precision to compile the model (Example Only)",
        "dialog_type": "Radiolist Dialog",
        "options": ["FP8", "FP16"],
        "type": "str",
        "default": "FP16",
        "depends": null
    },
    {
        "name": "batch",
        "note": "Batch Size",
        "dialog_type": "Input Dialog",
        "type": "str",
        "default": "4",
        "depends": null
    }
]
```


get_interact_profile可以获取任务Config信息和模型信息，供应商还可以在此API下生成json之外的一些选项。

- **get_best_batch_size()**
选择具有最佳批处理大小配置的接口。对于某些加速卡，可能有最佳的批处理大小使用。此接口可用于对模型进行初步分析并返回一个最佳bs列表。框架将评估此接口返回的列表。

### 实现 RuntimeBackend API
在当前版本中，需要实现的APIs如下：
```python title="runtime_backend.py"
class RuntimeBackend(object):
    def __init__(self):
        self.hardware_type = 'UnKnown'
        self.need_reload = False
        self.need_quant = False

    def version(self) -> str:
        """
        Return runtime backend version details
        """
        raise NotImplementedError("RuntimeBackend:version")

    def load(self, batch_size) -> str:
        """
        Return runtime backend version details
        """
        raise NotImplementedError("RuntimeBackend:load")

    def get_loaded_batch_size(self) -> int:
        """
        Get Currect batch size
        """
        raise NotImplementedError("RuntimeBackend:get_loaded_batch_size")

    def predict(self, data):
        """
        Run the compiled model and return the model output corresponding to the data.
        """
        raise NotImplementedError("RuntimeBackend:predict")

    def is_qs_mode_supported(self) -> bool:
        """
        Used to check whether QSv2 Runtime is enabled
        """
        return False

    def generate_qs_config(self) -> Dict[str, Any]:
        """
        Used only when is_qs_ported return True. Generate QS Config
        File for QSv2 Runtime
        """
        return None

    def benchmark(self, dataloader):
        """
        Performance Testing when qs mode is not enabled.
        """
        raise NotImplementedError("RuntimeBackend:benchmark")
```


- **load()**
加载对应Batch Size的模型。编译结果框架将传递给运行时后端，确保输入bs与compile返回的后端匹配。

- **predict()**
为单个预测调用编译后的产品。

- **is_qs_mode_supported()**
是否已连接到qs，如果已连接，则可以通过qs执行性能测试。

- **generate_qs_config()**
如果已支持qs，框架将调用此接口生成相应的qs配置。

- **benchmark()**
在QuickSilver准备好之前，框架用于调用此接口，并将基准传递给Runtime后端，运行时后端在接口中加载编译后的产品进行性能测试。
需要返回一个字典，其中可以包含如BS、QPS、AVG Latency、P99 Latency等信息，如下所示。

```python
"Performance": 
[
  {
      "BS": 1,
      "QPS": 2,
      "AVG_Latency": 1.2,
      "P99_Latency": 15,
  },
  {
      "BS": 1,
      "QPS": 2,
      "AVG_Latency": 1.2,
      "P99_Latency": 15,
  },
],
```

## 配置信息说明

传递给compile的配置包含三个部分，其在configs中的布局如下：

```json
configs = {
    "workload" : {...},
    "model_info" : {...},
    "interact_info" : {...},
}
```

### 任务信息:

我们从model_framework_precision.json获得的基本Config文件

```json title=bert-torch-fp32.json
{
    "model": "bert-torch-fp32",   //待评估的模型名称，需要与model_zoo名称对齐
    "test_perf": true,            //是否评估模型性能
    "test_accuracy": true,        //是否评估模型精度
    "test_numeric": true,         //精度：是否评估数字误差
    "clients": 3,                 //性能：提交数据的客户端线程数
    "iterations": 100,            //性能：每个线程提交的迭代次数
    "batch_sizes":[1,4,16],       //性能：每个线程提交数据时的批处理大小
    "fake_data": false,           //性能：使用虚假数据提交数据
    "data_percent": 50,           //精度：用于评估精度的数据集的百分比，[1-100]
}
```

### 模型信息
关于模型本身的信息。示例如下：
```json title=bert-tf-fp16.json
{
    "name": "bert-tf-fp16",                      //模型名称，通常与json文件保持一致
    "model_path": "model_zoo/bert/bert_fp16",    //模型在model_zoo中的相对路径
    "framework": "Tensorflow",                   //训练框架
    "framework_version": "2.4.0",                //训练框架的版本
    "model_format": "saved_model",               //模型格式
    "model_precision": "FP32",                   //模型精度
    "inputs": "input_ids:0",                     //模型输入
    "outputs": "logits:0",                       //模型输出
    "input_shape": {                             //输入形状
        "input_ids:0": [1, 384], 
    }, 
    "input_type": "FLOAT32",                     //输入类型
    "dataset_name": "squad",                     //数据集名称
    "max_batch_size": 64                         //最大批处理大小
}
```

## 交互信息

供应商希望收集的信息。示例如下：

```json
{
    "max_seq_len": 512,
    "try_dfs": true,
    "internal_quant_bits_int16": 12
}
```

交互配置信息如下
```json title=CPU.json
{
    "name": "max_seq_len",
    "note" : "模型Padding后的最长序列长度？",
    "dialog_type": "Input Dialog",
    "type": "int",
    "depends": "is_bert",
},
{
    "name": "try_dfs",
    "note": "是否存在多分支共享输入的Layer？",
    "dialog_type": "Yes/No Dialog",
    "type": "bool",
    "depends": null,
},
{
    "name" : "internal_quant_bits_int16",
    "note" : "int16时内部量化的位数，可以是12，13或14",
    "dialog_type": "Radiolist Dialog",
    "type" : "int",
    "default": "12",
    "options":["12", "13", "14"],
    "depends" : "accuracy_mode"
}
```

## 命名原则

从用户的使用方法开始，解释命名原则。

```bash
python3 lanuch.py --task xxx --hardware_type xxx
```

### Workload命名原则

```--task```
工作负载描述文件由参数 ``--task`` 指示，并且该参数是工作负载描述文件的前缀。例如，要评估 ``bert-tf-fp32.json``，参数应为 ``--task bert-tf-fp32``。

### 后端命名原则
```--hardware_type```
1. 新文件夹将以 ``--hardware_type`` 参数命名。命名规则为大写，例如，GRAPHCORE。
2. 在这个新文件夹中，应添加一个后端入口文件：``runtime_backend_xxx.py/compile_backend_xxx.py``。注意，这里的命名是小写的。例如，对于GRAPHCORE，名称将是：``runtime_backend_graphcore.py/compile_backend_graphcore.py``。
3. 在后端入口文件中，后端的主类名称应该遵循：``CompileBackendXXX()``, 例如，对于GRAPHCORE，名称应该是： ``RuntimeBackendGRAPHCORE/CompileBackendGRAPHCORE``；

### 模型配置命名原则

```workload：model```
任何新添加的模型描述文件都应与工作负载描述文件中的模型字段一致。例如，对于在 ``bert-torch-fp32.json`` 工作负载中定义的模型，相应的模型描述文件是：``model_zoo/bert-torch-fp32.json``。具体细节可以根据字段要求进行调整。

```json title=bert-torch-fp32.json
{
    "model": "bert-torch-fp32",    // 要评估的模型名称，必须与 model_zoo 名称匹配
    "test_perf": true,             // 是否应评估模型性能
    "test_accuracy": true,         // 是否应评估模型准确性
    "test_numeric": true,          // 是否应评估数值误差
    "clients": 3,                  // 将提交数据的客户端线程的数量
    "iterations": 100,             // 指定每个线程提交的迭代次数
    "batchsizes":[1,4,8,16,32,64], // 性能：每个线程提交数据时的批处理大小
    "data_percent": 50,            // 准确性：用于评估准确性的数据集的百分比，范围[1-100]
    "compile_only":false           // 是否只编译模型
}
```

### 数据集命名原则

```model_info: dataset_name```
如下所示的 model_info 内容中，“dataset_name”: "squad"，squad 是 datasets 文件夹下的文件夹名称。因此，数据集的名称需要与模型信息的描述保持一致。

```json title=bert-tf-fp16.json
{
    "name": "bert-tf-fp16",                    // 模型名称，通常与json文件一致
    "model_path": "model_zoo/bert/bert_fp16",  // 与model_zoo相对的模型路径
    "framework": "Tensorflow",                 // 训练框架
    "framework_version": "2.4.0",              // 训练框架版本
    "model_format": "saved_model",             // 模型格式
    "model_precision": "FP32",                 // 模型精度
    "inputs": "input_ids:0,",                  // 模型输入名称
    "outputs": "logits:0",                     // 模型输出名称
    "input_shape": {                           // 输入形状
        "input_ids:0": [1, 384], 
    },  
    "input_type": "FLOAT32",                   // 数据数据类型
    "dataset_name": "squad",                   // 指定的数据集，应与数据集命名保持一致
    "max_batch_size": 64,                      // 模型的最大批次大小  
}
```

## 厂商接入测试

供应商在完成后端接入后，可以运行以下代码来测试他们自己的后端，其中xxx是新添加的后端名称。详情请参考命名规范。

```bash
pip install -r requirements.txt
./run.sh --task resnet50-torch-fp32 --hardware_type xxx
```